home *** CD-ROM | disk | FTP | other *** search
- /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
-
- /* $Header: getc.c,v 2.5 85/08/22 16:02:44 timo Exp $ */
-
- /* B editor -- read key definitions from file */
-
- #include "b.h"
- #include "feat.h"
- #ifdef LINDA
- #include "b1mem.h"
- #define syserr EDsyserr
- #else !LINDA
- #define freemem(p) free(p)
- #endif !LINDA
- #include "file.h"
- #include "keys.h"
-
- #include <ctype.h>
-
- extern bool dflag;
-
- #define ESC '\033'
-
- /*
- This file contains a little parser for key definition files.
- To allow sufficient freedom in preparing such a file, a simple
- grammar has been defined according to which the file is parsed.
- The parsing process is extremely simple, as it can be done
- top-down using recursive descent.
-
-
- Lexical conventions:
-
- - Blanks between lexical symbols are gnored.
- - From '#' to end of line is comment (except inside strings).
- - Strings are delimited by single or double quotes and
- use the same escape sequences as C strings, plus:
- \e or \E means an ESCape ('\033').
- - Command names are like C identifiers ([a-zA-Z_][a-zA-Z0-9_]*).
- Upper/lower case distinction is significant.
- - numbers are octal or decimal integers in C-style (leading zero means octal)
- - After '^' a character is expected, this must be a letter or one of @^_[]\ .
-
- Syntax in modified BNF ([] mean 0 or 1, * means 0 or more, + means 1 or more):
-
- file: line*
- line: [def] [comment]
- def: commandname '=' rhs
- rhs: item+
- item: string | '^' character | number
-
-
- Notes:
-
- - A definition for command "term_init" defines a string to be sent
- TO the terminal at initialization time, e.g. to set programmable
- function key definitions. Similar for "term_done" on exiting.
- - Command names are conventional editor commands.
-
- */
-
-
- #ifndef LINDA
- /* Defines subroutine that used to be in the support levels: */
-
- Hidden string getmem(nbytes)
- unsigned nbytes;
- {
- string malloc();
- string pointer= malloc(nbytes);
-
- if (pointer == NULL)
- syserr("memory full in initkeys");
- return pointer;
- }
-
- Hidden string regetmem(pp, nbytes)
- string *pp;
- unsigned nbytes;
- {
- *pp= realloc(*pp, nbytes);
- if (*pp == NULL)
- syserr("memory full in initkeys (regetmem)");
- }
- #endif !LINDA
-
-
- #define COMMENT '#' /* Not B-like but very UNIX-like */
- #define MAXDEFS 100
-
- Hidden FILE *fp; /* File from which to read */
- Hidden string filename; /* File name for error messages */
- Hidden char nextc; /* Next character to be analyzed */
- Hidden bool eof; /* EOF seen? */
- Hidden int lcount; /* Current line number */
- Hidden bool errcount; /* Number of errors detected */
-
-
- struct tabent {
- int code;
- string name;
- string def;
- };
-
- /* Table of key definitions, mostly filled by reading definitions from a file.
- The "I" macro has two arguments: the default for termcap and that for
- the IBM PC. It expands to either depending on whether IBMPC is defined.
- 'def' fields initialized with a string starting with '=' are termcap names,
- and are replaced by the corresponding termcap entry (NULL if none).
- On the IBM PC, 'extended codes' are by convention a null character
- followed by another character (usually the scan code). Since the null
- character is rather unsuitable for use in C strings, we use \377 (hex FF)
- instead, a code which has no assigned graphic is the extended IBM PC
- character set. E.g., F1 is 0-59, which we encode as \377\073 (since
- \073 is octal for 59 decimal). For the exact codes, see for instance the
- BASIC 2.0 manual, appendix G, or the XT Technical Reference, page 2-14.
- */
-
- #ifdef IBMPC
- #define I(tc, ibm) ibm
- #else !IBMPC
- #define I(tc, ibm) tc
- #endif !IBMPC
-
- Visible struct tabent deftab[MAXDEFS] = {
- /* General rule:
- unix => ctrl-x
- IBM => alt-x
- where x is first letter of command name
- */
- {0377, "ignore", NULL}, /* Entry to ignore a key */
- {COPY, "copy", I(NULL, "\377\056")},
- {DELETE, "delete", I(NULL, "\377\040")},
- {DELETE, "delete", I(NULL, "\377\123")}, /* IBM DEL key */
- {ACCEPT, "accept", I(NULL, "\377\022")}, /* ^E, alt-E */
- {ACCEPT, "end", I(NULL, "\377\117")}, /* IBM END key */
- {'\t', "tab", NULL}, /* = ACCEPT in Bed, insert tab in Linda */
- {UNDO, "undo"}, /* Always backspace = ^H */
- {REDRAW, "redraw", I(NULL, "\377\046")}, /* ^L, alt-L */
- {REDRAW, "look"},
- {RETURN, "newline"}, /* Always ^M */
- {REDO, "redo", I(NULL, "\177")}, /* IBM ctrl-BS = ASCII 177 (DEL) */
- {EXIT, "exit", I(NULL, "\377\055")}, /* ^X, alt-X */
-
- #ifdef RECORDING
- /*
- * The IBM-PC has a problem here in ANSI.SYS mode: ctrl-P is
- * unusable because it means Print Screen, and alt-R is unusable
- * because it transmits 0, 19 but 19 is ctrl-S which means stop
- * output :-(.
- * The only reasonable place to put the things would then be on
- * function keys. You should do this in the key definitions file. (?)
- */
- {PLAYBACK, "play", I(NULL, "\377\031")},
- {PLAYBACK, "playback", I(NULL, "\377\031")},
- {RECORD, "record", I(NULL, "\377\023")},
- #endif RECORDING
-
- #ifdef LINDA
- {BFIND, "bfind", I(NULL, "\377\060")},
- {FIND, "find", I(NULL, "\377\041")},
- {GLOBAL, "global", I(NULL, "\377\042")},
- {JOIN, "join", I(NULL, "\377\044")},
- {TOGGLE, "toggle", I(NULL, "\377\024")},
- {YANK, "yank", I(NULL, "\377\025")},
- {LITERAL, "literal", I(NULL, "\377\057")}, /* ^V, alt-V */
- #endif LINDA
-
- {WIDEN, "widen", I("=k1", "\377\073")}, /* IBM F1 */
- {NARROW, "narrow", I("=k2", "\377\075")}, /* IBM F3 (!!!) */
- {NARROW, "first"},
- {RNARROW, "rnarrow", I("=k3", "\377\076")}, /* IBM F4 (!!!) */
- {RNARROW, "last"},
- {EXTEND, "extend", I("=k4", "\377\074")}, /* IBM F2 (!!!) */
- {UPARROW, "up", I("=ku", "\377\110")},
- {UPLINE, "upline", I("=k5", "\377\110")},
- {LEFTARROW, "left", I("=kl", "\377\113")},
- {PREVIOUS, "previous", I("=k6", NULL)},
- {RITEARROW, "right", I("=kr", "\377\115")},
- {NEXT, "next", I("=k7", NULL)},
- {DOWNARROW, "down", I("=kd", "\377\120")},
- {DOWNLINE, "downline", I("=k8", "\377\120")},
-
- {GOTO, "goto", I("\033g", NULL)}, /* Doesn't exist on IBM */
- #ifdef HELPFUL
- {HELP, "help", I("\033?", "\377\104")}, /* ESC ?, IBM F10 */
- #endif HELPFUL
-
- {0, "term_init", I("=ks", NULL)},
- {0, "term_done", I("=ke", NULL)},
- };
-
- #undef I
-
- Hidden int ndefs;
-
-
- Hidden Procedure err(fmt, arg)
- string fmt, arg;
- {
- if (errcount == 0)
- fprintf(stderr, "Errors in key definitions file:\n");
- ++errcount;
- fprintf(stderr, "%s, line %d: ", filename, lcount);
- fprintf(stderr, fmt, arg);
- fprintf(stderr, "\n");
- }
-
- Hidden Procedure adv()
- {
- int c;
-
- if (eof)
- return;
- c= getc(fp);
- if (c == EOF) {
- nextc= '\n';
- eof= Yes;
- }
- else {
- nextc= c;
- if (c == '\n')
- ++lcount;
- }
- }
-
- Hidden Procedure skipsp()
- {
- while (nextc == ' ' || nextc == '\t')
- adv();
- }
-
- Hidden int lookup(name)
- string name;
- {
- int i;
-
- for (i= 0; i < ndefs; ++i) {
- if (deftab[i].name != NULL && strcmp(name, deftab[i].name) == 0)
- return i;
- }
- return -1;
- }
-
- Hidden Procedure store(code, name, def)
- int code;
- string name;
- string def;
- {
- struct tabent *d, *last= deftab+ndefs;
- string p, q;
-
- /* Undefine conflicting definitions. Conflicts arise
- when a command definition is an initial subsequence
- of another, or vice versa. Key definitions (code < 0)
- are not undefined. */
- if (code > 0) {
- for (d= deftab; d < last; ++d) {
- if (d->code >= 0 && d->def != NULL) {
- for (p= def, q= d->def; *p == *q; ++p, ++q) {
- if (*p == '\0' || *q == '\0') {
- d->def= NULL;
- break;
- }
- }
- }
- }
- }
-
- /* Find a free slot with the same code and NULL definition */
- /* (For code == 0, the name must match instead of the code,
- and the definition need not be NULL) */
- for (d= deftab; d < last; ++d) {
- if (code == 0 ? strcmp(name, d->name) == 0
- : (d->code == code && d->def == NULL))
- break;
- }
- if (d == last) { /* Extend definition table */
- if (ndefs >= MAXDEFS) {
- err("Too many key definitions", "");
- return;
- }
- ++ndefs;
- d->code= code;
- d->name= name;
- }
- d->def= def;
- }
-
- Hidden string savestr(s)
- string s;
- {
- string new;
-
- new= getmem((unsigned) (strlen(s) + 1));
- strcpy(new, s);
- return new;
- }
-
- Hidden Procedure append(to, item)
- string *to, item;
- {
- int len= strlen(*to) + strlen(item) + 1;
- regetmem(to, len);
- strcat(*to, item);
- }
-
- Hidden string getname()
- {
- char buffer[20];
- string bp;
-
- if (!isalpha(nextc) && nextc != '_') {
- err("No name where expected", "");
- return NULL;
- }
- for (bp= buffer; isalnum(nextc) || nextc == '_'; ) {
- if (bp < buffer + sizeof buffer - 1)
- *bp++ = nextc;
- adv();
- }
- *bp= '\0';
- return savestr(buffer);
- }
-
- Hidden int getnumber()
- {
- int base= (nextc == '0') ? 8 : 10;
- int i= 0;
- int d;
-
- for (;; adv()) {
- d= nextc-'0';
- if (d < 0 || d > 9)
- break;
- if (d > base) {
- err("8 or 9 in octal number", "");
- return 0;
- }
- i= i*base + d;
- }
- return i;
- }
-
- Hidden string getstring()
- {
- char buf[256]; /* Arbitrary limit */
- char quote= nextc;
- char c;
- int len= 0;
-
- adv();
- while (nextc != quote) {
- if (nextc == '\n') {
- err("closing string quote not found", "");
- return NULL;
- }
- if (nextc != '\\') {
- c= nextc;
- adv();
- }
- else {
- adv();
- switch (nextc) {
-
- case 'r': c= '\r'; adv(); break;
- case 'n': c= '\n'; adv(); break;
- case 'b': c= '\b'; adv(); break;
- case 't': c= '\t'; adv(); break;
- case 'f': c= '\f'; adv(); break;
-
- case 'E':
- case 'e': c= ESC; adv(); break;
-
- case '0': case '1': case '2': case '3':
- case '4': case '5': case '6': case '7':
- c= nextc-'0';
- adv();
- if (nextc >= '0' && nextc < '8') {
- c= 8*c + nextc-'0';
- adv();
- if (nextc >= '0' && nextc < '8') {
- c= 8*c + nextc-'0';
- adv();
- }
- }
- break;
-
- default: c=nextc; adv(); break;
-
- }
- }
- if (len >= sizeof buf) {
- err("string too long", "");
- return NULL;
- }
- buf[len++]= c;
- }
- adv();
- buf[len]= '\0';
- return savestr(buf);
- }
-
- Hidden string getitem()
- {
- char buf[2];
- string keyname;
- int i;
-
- switch (nextc) {
- case '"':
- case '\'':
- return getstring();
- case '^':
- adv();
- if (isalpha(nextc) || index("@^_[]\\?", nextc)) {
- if (nextc == '?')
- buf[0]= '\177';
- else
- buf[0]= nextc & 037;
- buf[1]= '\0';
- adv();
- return savestr(buf);
- }
- err("Invalid character after '^'", "");
- return NULL;
- default:
- if (isdigit(nextc)) {
- buf[0]= getnumber();
- buf[1]= '\0';
- return savestr(buf);
- }
- if (isalpha(nextc) || nextc == '_') {
- keyname= getname(); /* Cannot fail */
- if (strlen(keyname) == 1)
- return savestr(keyname);
- /* Single letters stand for themselves */
- i= lookup(keyname);
- if (i < 0 || deftab[i].code <= 0) {
- err("%s: not a key name", keyname);
- freemem(keyname);
- return NULL;
- }
- else if (deftab[i].def == NULL) {
- err("%s: undefined key", keyname);
- freemem(keyname);
- return NULL;
- }
- else
- return savestr(deftab[i].def);
- }
- err("Invalid item", "");
- return NULL;
- }
- }
-
- Hidden string getrhs()
- {
- string first, item;
-
- skipsp();
- first= getitem();
- if (first != NULL) {
- for (;;) {
- skipsp();
- if (nextc == '\n' || nextc == COMMENT)
- break;
- item= getitem();
- if (item == NULL) {
- freemem(first);
- return NULL;
- }
- append(&first, item);
- freemem(item);
- }
- }
- return first;
- }
-
- Hidden Procedure getdef()
- {
- string name;
- int key;
- string rhs;
-
- name= getname();
- if (name == NULL)
- return;
- skipsp();
- if (nextc != '=') {
- err("Command name %s not followed by '='", name);
- return;
- }
- key= lookup(name);
- if (key < 0) {
- err("Unknown command: %s", name);
- return;
- }
- if (deftab[key].code < 0) {
- err("No redefinition of %s allowed", name);
- return;
- }
- adv();
- rhs= getrhs();
- if (rhs != NULL)
- store(deftab[key].code, name, rhs);
- }
-
- Hidden Procedure getline()
- {
- adv();
- skipsp();
- if (nextc != COMMENT && nextc != '\n')
- getdef();
- while (nextc != '\n')
- adv();
- }
-
- #ifndef NDEBUG
- Hidden Procedure dump(where)
- string where;
- {
- int i;
- string s;
-
- printf("\nDump of key definitions %s.\n\n", where);
- printf("Code Name Definition\n");
- for (i= 0; i < ndefs; ++i) {
- printf("%04o ", deftab[i].code);
- if (deftab[i].name != NULL)
- printf("%-15s ", deftab[i].name);
- else
- printf("%16s", "");
- s= deftab[i].def;
- if (s != NULL) {
- for (; *s != '\0'; ++s) {
- if (isascii(*s) && (isprint(*s) || *s == ' '))
- fputc(*s, stdout);
- else
- printf("\\%03o", *s&0377);
- }
- }
- printf("\n");
- }
- fflush(stdout);
- }
- #endif !NDEBUG
-
- Hidden Procedure countdefs()
- {
- struct tabent *d;
-
- d= deftab;
- while (d->name != NULL || d->code != 0 || d->def != NULL) {
- ++d;
- if (d >= deftab+MAXDEFS)
- syserr("too many predefined keys");
- }
- ndefs= d-deftab;
- }
-
- Hidden Procedure process()
- {
- errcount= 0;
- lcount= 1;
- eof= No;
- do {
- getline();
- } while (!eof);
- }
-
- Hidden bool try(dir, file, type)
- string dir, file, type;
- {
- char buffer[200];
-
- #ifdef IBMPC
- sprintf(buffer, "%.150s\\%.9s%.3s", dir, file, type);
- #else !IBMPC
- sprintf(buffer, "%.150s/%.20s%.20s", dir, file, type);
- #endif !IBMPC
- fp= fopen(buffer, "r");
- if (fp == NULL)
- return No;
- filename= buffer;
- process();
- fclose(fp);
- #ifndef NDEBUG
- if (dflag)
- dump("after try");
- #endif NDEBUG
- return Yes;
- }
-
- #ifndef IBMPC
- Hidden Procedure readtermcap()
- {
- string tgetstr();
- char buffer[1024]; /* Constant dictated by termcap manual entry */
- static char area[1024];
- string endarea= area;
- string anentry;
- struct tabent *d, *last;
-
- switch (tgetent(buffer, getenv("TERM"))) {
-
- default:
- fprintf(stderr, "*** Bad tgetent() return value.\n");
- /* Fall through */
- case -1:
- fprintf(stderr, "*** Can't read termcap.\n");
- /* Fall through again */
- case 0:
- fprintf(stderr, "*** No description for your terminal.\n");
- exit(1);
-
- case 1:
- break;
- }
- last= deftab+ndefs;
- for (d= deftab; d < last; ++d) {
- if (d->def != NULL && d->def[0] == '=') {
- anentry= tgetstr(d->def+1, &endarea);
- if (anentry != NULL && anentry[0] != '\0')
- d->def= anentry;
- else
- d->def= NULL;
- }
- }
- }
- #endif !IBMPC
-
- Visible Procedure initkeys()
- {
- string term= NULL;
-
- countdefs();
- #ifndef NDEBUG
- if (dflag)
- dump("before termcap");
- #endif NDEBUG
- #ifndef IBMPC
- readtermcap();
- #ifndef NDEBUG
- if (dflag)
- dump("after termcap");
- #endif NDEBUG
- term= getenv("TERM");
- if (term != NULL && term[0] == '\0')
- term= NULL;
- #endif !IBMPC
- #ifdef DEBUG
- /* Try in the current directory. Only for debugging porpoises. */
- if (term != NULL)
- if (try(".", keyfile, term)) return;
- #endif DEBUG
- if (term != NULL) {
- if (try(homedir, keyfile, term)) return;
- if (try(libdir, keyfile, term)) return;
- }
- #ifdef DEBUG
- if (try(".", keyfile, deftype)) return;
- #endif DEBUG
- if (try(homedir, keyfile, deftype)) return;
- if (try(libdir, keyfile, deftype)) return;
- #ifndef NDEBUG
- printf("[No key definitions file found, using defaults.]\n");
- #endif !NDEBUG
- }
-
-
- /* Output a named string to the terminal */
-
- Hidden Procedure outstring(name)
- string name;
- {
- int i= lookup(name);
- string def;
-
- if (i >= 0 && (def= deftab[i].def) != NULL)
- fputs(def, stdout);
- }
-
-
- /* Output the terminal's initialization sequence, if any. */
-
- Visible Procedure
- initgetc()
- {
- outstring("term_init");
- }
-
-
- /* Output a sequence, if any, to return the terminal to a 'normal' state. */
-
- Visible Procedure endgetc()
- {
- outstring("term_done");
- }
-
-
- /* Read a command from the keyboard, decoding composite key definitions. */
-
- #ifndef IBMPC
- /* Strip high bit from input characters (matters only on PWB systems?) */
- #define getch() (getchar() & 0177)
- #endif !IBMPC
-
- Visible int inchar()
- {
- int c;
- struct tabent *d, *last;
- char buffer[100];
- int len;
-
- c= getch();
- if (c == EOF)
- return c;
- #ifdef IBMPC
- if (c == 0)
- c= 0377;
- #endif IBMPC
- last= deftab+ndefs;
- for (d= deftab; d < last; ++d) {
- if (d->code > 0 && d->def != NULL && c == (d->def[0] & 0377))
- break;
- }
- if (d == last) {
- if (c == ESC) {
- /* Kludge to make ESC-char by default equal to
- char|MASK -- the command definitions do the rest:
- e.g. WIDEN is 'w'|MASK, so ESC-w means WIDEN. */
- c= getch();
- if (c == EOF)
- return EOF;
- return (c&0177) | MASK;
- }
- return c;
- }
- if (d->def[1] == '\0')
- return d->code;
- buffer[0]= c;
- len= 1;
- for (;;) {
- c= getch();
- if (c == EOF)
- return EOF;
- buffer[len]= c;
- if (len < sizeof buffer - 1)
- ++len;
- for (d= deftab; d < last; ++d) {
- if (d->code > 0 && d->def != NULL
- && strncmp(buffer, d->def, len) == 0)
- break;
- }
- if (d == last) {
- if (buffer[0] == ESC && len == 2) {
- /* Same kludge as above */
- return c&0177 | MASK;
- }
- return 0377; /* Hope this rings a bell */
- }
- if (d->def[len] == '\0')
- return d->code;
- }
- }
-